package com.jay.media.music.core.provider

import android.annotation.SuppressLint
import android.content.Context
import android.content.res.Resources
import android.media.MediaDescription
import android.media.MediaMetadata
import android.media.browse.MediaBrowser
import android.net.Uri
import android.util.Log
import com.jay.media.music.core.R
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File
import java.util.concurrent.ConcurrentHashMap

/**
 * Create by xuzhou4
 * Date 2021/11/8
 */

class MusicProvider(var mContext: Context) {

    companion object {
        private const val TAG = "MusicProvider"
    }

    enum class State {
        NOT_INITIALIZED, INITIALIZING, INITIALIZED
    }

    interface Callback {
        fun onScanFinish(success: Boolean)
    }

    private var mMusicSource: MusicProviderSource = LocalMusicSource(mContext)
    private var mCurrentState: State = State.NOT_INITIALIZED

    private var mMusicMap: MutableMap<String, MediaMetadata> =
        ConcurrentHashMap<String, MediaMetadata>()
    private var mMusicListByGenre: MutableMap<String, MutableList<MediaMetadata>> =
        ConcurrentHashMap()
    private var mMusicListByArtist: MutableMap<String, MutableList<MediaMetadata>> =
        ConcurrentHashMap()
    private var mMusicListByAlbum: MutableMap<String, MutableList<MediaMetadata>> =
        ConcurrentHashMap()

    /**
     * 获取本地音乐列表
     */
    fun retrieveMediaAsync(callback: Callback?) {
        CoroutineScope(Dispatchers.IO).launch {
            Log.d(TAG, "retrieveMediaAsync")

            if (mCurrentState == State.INITIALIZED) {
                callback?.onScanFinish(mCurrentState == State.INITIALIZED)
                return@launch
            }

            try {
                Log.d(TAG, "retrieveMediaAsync start")
                if (mCurrentState == State.NOT_INITIALIZED) {
                    mCurrentState = State.INITIALIZING

                    val tracks = mMusicSource.iterator()
                    while (tracks.hasNext()) {
                        val item: MediaMetadata = tracks.next()

                        val musicId: String = item.getString(MediaMetadata.METADATA_KEY_MEDIA_ID)
                        mMusicMap[musicId] = item
                    }
                    buildListsByGenre()
                    buildListsByArtist()
                    buildListsByAlbum()

                    Log.d(TAG, "retrieveMediaAsync success")
                    mCurrentState = State.INITIALIZED
                    callback?.onScanFinish(mCurrentState == State.INITIALIZED)
                }
            } catch (e: Exception) {
                e.printStackTrace()

                Log.d(TAG, "retrieveMediaAsync exception")
                mCurrentState = State.NOT_INITIALIZED
                callback?.onScanFinish(mCurrentState == State.INITIALIZED)
            } finally {
                Log.d(TAG, "retrieveMediaAsync end")
            }
        }
    }

    @Synchronized
    private fun buildListsByGenre() {
        val newMusicListByGenre: MutableMap<String, MutableList<MediaMetadata>> =
            ConcurrentHashMap()
        for (m in mMusicMap.values) {
            val genre: String = m.getString(MediaMetadata.METADATA_KEY_GENRE) ?: "unknown"
            var list = newMusicListByGenre[genre]
            if (list == null) {
                list = java.util.ArrayList()
                newMusicListByGenre[genre] = list
            }
            list.add(m)
        }
        mMusicListByGenre = newMusicListByGenre
    }

    @Synchronized
    private fun buildListsByArtist() {
        val newMusicListByArtist: MutableMap<String, MutableList<MediaMetadata>> =
            ConcurrentHashMap()
        for (m in mMusicMap.values) {
            val artist: String = m.getString(MediaMetadata.METADATA_KEY_ARTIST) ?: "unknown"
            var list = newMusicListByArtist[artist]
            if (list == null) {
                list = java.util.ArrayList()
                newMusicListByArtist[artist] = list
            }
            list.add(m)
        }
        mMusicListByArtist = newMusicListByArtist
    }

    @Synchronized
    private fun buildListsByAlbum() {
        val newMusicListByAlbum: MutableMap<String, MutableList<MediaMetadata>> =
            ConcurrentHashMap()
        for (m in mMusicMap.values) {
            val album: String = m.getString(MediaMetadata.METADATA_KEY_ALBUM) ?: "unknown"
            var list = newMusicListByAlbum[album]
            if (list == null) {
                list = java.util.ArrayList()
                newMusicListByAlbum[album] = list
            }
            list.add(m)
        }
        mMusicListByAlbum = newMusicListByAlbum
    }


    fun getChildren(
        mediaID: String,
        resources: Resources
    ): MutableList<MediaBrowser.MediaItem> {
        val mediaItems: MutableList<MediaBrowser.MediaItem> = ArrayList()
        if (!MusicIDHelper.isBrowseable(mediaID)) {
            return mediaItems
        }

        if (MusicIDHelper.MEDIA_ID_MUSICS == mediaID) {
            for (mutableEntry in mMusicMap) {
                mediaItems.add(createMediaItem(mutableEntry.value))
            }
        } else if (MusicIDHelper.MEDIA_ROOT_ID == mediaID) {
            mediaItems.addAll(createBrowsableMediaItemForRoot(resources))
        } else if (MusicIDHelper.MEDIA_ID_MUSICS_BY_GENRE == mediaID) {
            val genres = getGenres()
            Log.w(TAG, "genres $genres")
            for (genre in genres) {
                mediaItems.add(createBrowsableMediaItemForGenre(genre, resources))
            }
        } else if (mediaID.startsWith(MusicIDHelper.MEDIA_ID_MUSICS_BY_GENRE)) {
            val genre = MusicIDHelper.getHierarchy(mediaID)[1]!!
            for (metadata in getMusicsByGenre(genre)) {
                mediaItems.add(createMediaItemByGenre(metadata))
            }
        } else if (MusicIDHelper.MEDIA_ID_MUSICS_BY_ARTIST == mediaID) {
            val artists = getArtists()
            Log.w(TAG, "artists $artists")
            for (artist in artists) {
                mediaItems.add(createBrowsableMediaItemForArtist(artist, resources))
            }
        } else if (mediaID.startsWith(MusicIDHelper.MEDIA_ID_MUSICS_BY_ARTIST)) {
            val artist = MusicIDHelper.getHierarchy(mediaID)[1]!!
            for (metadata in getMusicsByArtist(artist)) {
                mediaItems.add(createMediaItemByArtist(metadata))
            }
        } else if (MusicIDHelper.MEDIA_ID_MUSICS_BY_ALBUM == mediaID) {
            val albums = getAlbums()
            Log.w(TAG, "albums $albums")
            for (album in albums) {
                mediaItems.add(
                    createBrowsableMediaItemForAlbum(
                        album, resources,
                        mMusicListByAlbum[album]?.get(0)
                            ?.getString(MediaMetadata.METADATA_KEY_ALBUM_ART_URI) ?: ""
                    )
                )
            }
        } else if (mediaID.startsWith(MusicIDHelper.MEDIA_ID_MUSICS_BY_ALBUM)) {
            val album = MusicIDHelper.getHierarchy(mediaID)[1]!!
            for (metadata in getMusicsByAlbum(album)) {
                mediaItems.add(createMediaItemByAlbum(metadata))
            }
        } else {
            Log.w(TAG, "getChildren: Skipping unmatched mediaId: $mediaID")
        }

        return mediaItems
    }

    private fun createBrowsableMediaItemForRoot(resources: Resources): List<MediaBrowser.MediaItem> {
        val list = ArrayList<MediaBrowser.MediaItem>()
        val description = MediaDescription.Builder()
            .setMediaId(MusicIDHelper.MEDIA_ID_MUSICS_BY_GENRE)
            .setTitle(resources.getString(R.string.browse_genres))
            .setSubtitle(resources.getString(R.string.browse_genre_subtitle))
            .setIconUri(
                Uri.parse(
                    "android.resource://" + mContext.packageName + "/drawable/ic_by_genre"
                )
            )
            .build()
        list.add(
            MediaBrowser.MediaItem(
                description,
                MediaBrowser.MediaItem.FLAG_BROWSABLE
            )
        )

        val artistDescription = MediaDescription.Builder()
            .setMediaId(MusicIDHelper.MEDIA_ID_MUSICS_BY_ARTIST)
            .setTitle(resources.getString(R.string.browse_artist))
            .setSubtitle(resources.getString(R.string.browse_artist_subtitle))
            .setIconUri(
                Uri.parse(
                    "android.resource://" + mContext.packageName + "/drawable/ic_by_artist"
                )
            )
            .build()
        list.add(
            MediaBrowser.MediaItem(
                artistDescription,
                MediaBrowser.MediaItem.FLAG_BROWSABLE
            )
        )

        val albumDescription = MediaDescription.Builder()
            .setMediaId(MusicIDHelper.MEDIA_ID_MUSICS_BY_ALBUM)
            .setTitle(resources.getString(R.string.browse_album))
            .setSubtitle(resources.getString(R.string.browse_album_subtitle))
            .setIconUri(
                Uri.parse(
                    "android.resource://" + mContext.packageName + "/drawable/ic_by_album"
                )
            )
            .build()
        list.add(
            MediaBrowser.MediaItem(
                albumDescription,
                MediaBrowser.MediaItem.FLAG_BROWSABLE
            )
        )

        return list
    }

    private fun getGenres(): Iterable<String?> {
        return if (mCurrentState != State.INITIALIZED) {
            emptyList<String>()
        } else mMusicListByGenre.keys
    }

    private fun createBrowsableMediaItemForGenre(
        genre: String?,
        resources: Resources
    ): MediaBrowser.MediaItem {
        val description = MediaDescription.Builder()
            .setMediaId(
                MusicIDHelper.createMediaID(
                    null,
                    MusicIDHelper.MEDIA_ID_MUSICS_BY_GENRE,
                    genre
                )
            )
            .setTitle(genre)
            .setSubtitle(
                resources.getString(
                    R.string.browse_musics_by_genre_subtitle, genre
                )
            )
            .build()
        return MediaBrowser.MediaItem(
            description,
            MediaBrowser.MediaItem.FLAG_BROWSABLE
        )
    }

    /**
     * Get music tracks of the given genre
     *
     */
    private fun getMusicsByGenre(genre: String?): Iterator<MediaMetadata> {
        var list: MutableList<MediaMetadata> = ArrayList()
        if (mCurrentState == State.INITIALIZED &&
            mMusicListByGenre.containsKey(genre)
        ) {
            mMusicListByGenre[genre]?.also {
                list = it
            }
        }
        return list.iterator()
    }

    private fun createMediaItemByGenre(metadata: MediaMetadata): MediaBrowser.MediaItem {
        val genre = metadata.getString(MediaMetadata.METADATA_KEY_GENRE)
        val hierarchyAwareMediaID = MusicIDHelper.createMediaID(
            metadata.description.mediaId,
            MusicIDHelper.MEDIA_ID_MUSICS_BY_GENRE,
            genre
        )
        val copy = MediaMetadata.Builder(metadata)
            .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, hierarchyAwareMediaID)
            .putString(
                MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI,
                metadata.getString(MediaMetadata.METADATA_KEY_ALBUM_ART_URI)
            )
            .putString(
                MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE,
                metadata.getString(MediaMetadata.METADATA_KEY_ARTIST)
            )
            .build()
        return MediaBrowser.MediaItem(
            copy.description,
            MediaBrowser.MediaItem.FLAG_PLAYABLE
        )
    }

    /**
     * Return the MediaMetadataCompat for the given musicID.
     *
     * @param musicId The unique, non-hierarchical music ID.
     */
    fun getMusic(musicId: String?): MediaMetadata? {
        return if (mMusicMap.containsKey(musicId)) {
            mMusicMap[musicId]
        } else null
    }


    private fun getArtists(): Iterable<String?> {
        return if (mCurrentState != State.INITIALIZED) {
            emptyList<String>()
        } else mMusicListByArtist.keys
    }

    private fun createBrowsableMediaItemForArtist(
        artist: String?,
        resources: Resources
    ): MediaBrowser.MediaItem {
        val description = MediaDescription.Builder()
            .setMediaId(
                MusicIDHelper.createMediaID(
                    null,
                    MusicIDHelper.MEDIA_ID_MUSICS_BY_ARTIST,
                    artist
                )
            )
            .setTitle(artist)
            .setSubtitle(
                resources.getString(
                    R.string.browse_musics_by_artist_subtitle, artist
                )
            )
            .build()
        return MediaBrowser.MediaItem(
            description,
            MediaBrowser.MediaItem.FLAG_BROWSABLE
        )
    }

    private fun getMusicsByArtist(artist: String?): Iterator<MediaMetadata> {
        var list: MutableList<MediaMetadata> = ArrayList()
        if (mCurrentState == State.INITIALIZED &&
            mMusicListByArtist.containsKey(artist)
        ) {
            mMusicListByArtist[artist]?.also {
                list = it
            }
        }
        return list.iterator()
    }

    @SuppressLint("WrongConstant")
    private fun createMediaItemByArtist(metadata: MediaMetadata): MediaBrowser.MediaItem {
        val artist = metadata.getString(MediaMetadata.METADATA_KEY_ARTIST)
        val hierarchyAwareMediaID = MusicIDHelper.createMediaID(
            metadata.description.mediaId,
            MusicIDHelper.MEDIA_ID_MUSICS_BY_ARTIST,
            artist
        )
        val copy = MediaMetadata.Builder(metadata)
            .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, hierarchyAwareMediaID)
            .putString(
                MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI,
                metadata.getString(MediaMetadata.METADATA_KEY_ALBUM_ART_URI)
            )
            .putString(
                MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE,
                metadata.getString(MediaMetadata.METADATA_KEY_ARTIST)
            )
            .build()
        return MediaBrowser.MediaItem(
            copy.description,
            MediaBrowser.MediaItem.FLAG_PLAYABLE
        )
    }


    private fun getAlbums(): Iterable<String?> {
        return if (mCurrentState != State.INITIALIZED) {
            emptyList<String>()
        } else mMusicListByAlbum.keys
    }

    private fun createBrowsableMediaItemForAlbum(
        album: String?,
        resources: Resources,
        albumArtUri: String
    ): MediaBrowser.MediaItem {
        val description = MediaDescription.Builder()
            .setMediaId(
                MusicIDHelper.createMediaID(
                    null,
                    MusicIDHelper.MEDIA_ID_MUSICS_BY_ALBUM,
                    album
                )
            )
            .setTitle(album)
            .setSubtitle(
                resources.getString(
                    R.string.browse_musics_by_album_subtitle, album
                )
            )
            .setIconUri(Uri.fromFile(File(albumArtUri)))
            .build()
        return MediaBrowser.MediaItem(
            description,
            MediaBrowser.MediaItem.FLAG_BROWSABLE
        )
    }

    private fun getMusicsByAlbum(album: String?): Iterator<MediaMetadata> {
        var list: MutableList<MediaMetadata> = ArrayList()
        if (mCurrentState == State.INITIALIZED &&
            mMusicListByAlbum.containsKey(album)
        ) {
            mMusicListByAlbum[album]?.also {
                list = it
            }
        }
        return list.iterator()
    }

    @SuppressLint("WrongConstant")
    private fun createMediaItemByAlbum(metadata: MediaMetadata): MediaBrowser.MediaItem {
        val album = metadata.getString(MediaMetadata.METADATA_KEY_ALBUM)
        val hierarchyAwareMediaID = MusicIDHelper.createMediaID(
            metadata.description.mediaId,
            MusicIDHelper.MEDIA_ID_MUSICS_BY_ALBUM,
            album
        )
        val copy =
            MediaMetadata.Builder(metadata)
                .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, hierarchyAwareMediaID)
                .putString(
                    MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI,
                    metadata.getString(MediaMetadata.METADATA_KEY_ALBUM_ART_URI)
                )
                .putString(
                    MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE,
                    metadata.getString(MediaMetadata.METADATA_KEY_ARTIST)
                )
                .build()

        return MediaBrowser.MediaItem(
            copy.description,
            MediaBrowser.MediaItem.FLAG_PLAYABLE
        )
    }

    @SuppressLint("WrongConstant")
    private fun createMediaItem(metadata: MediaMetadata): MediaBrowser.MediaItem {
        val hierarchyAwareMediaID = MusicIDHelper.createMediaID(
            metadata.description.mediaId,
            MusicIDHelper.MEDIA_ID_MUSICS
        )

        val copy =
            MediaMetadata.Builder(metadata)
                .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, hierarchyAwareMediaID)
                .putString(
                    MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI,
                    metadata.getString(MediaMetadata.METADATA_KEY_ALBUM_ART_URI)
                )
                .putString(
                    MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE,
                    metadata.getString(MediaMetadata.METADATA_KEY_ARTIST)
                )
                .build()

        return MediaBrowser.MediaItem(
            copy.description,
            MediaBrowser.MediaItem.FLAG_PLAYABLE
        )
    }

    /**
     * 搜索音乐、艺术家、专辑
     */
    fun search(keyword: String) {

    }

    private fun searchMusic(keyword: String): MutableList<MediaMetadata> {
        if (keyword.isBlank()) {
            return ArrayList()
        }
        return mMusicMap.values.filter { v ->
            v.getString(MediaMetadata.METADATA_KEY_TITLE).contains(keyword)
        }.toMutableList()
    }

    private fun searchArtist(keyword: String) {

    }

    private fun searchAlbum(keyword: String) {

    }
}